<?php
declare (strict_types=1);

namespace app\scan_bag\service;

use app\admin\model\base\User;
use app\admin\model\iot\Device;
use app\admin\model\iot\ScanBagDay;
use app\admin\model\iot\ScanBagOrder;
use app\admin\model\iot\ScanBagWay;
use app\BaseService;
use emqx\Emqx;
use gps\LatLon;
use iot\DeviceClient;
use job\ThinkQueueJob;
use pay\WechatPay;
use redis\RedisTools2;
use think\facade\Cache;
use wechat\ScanBagMiniProgram;
use wechat\WsOfficialAccount;

/**
 * V2取袋公用服务层
 */
class ScanBagV2Service extends BaseService
{

    /**
     * 无塑地球微信H5用户登录
     * @param string $code
     * @return array
     */
    public static function wechatH5Login(string $code): array
    {
        $obj = new WsOfficialAccount();
        $CodeInfo = $obj->userFromCode($code);
        if (!$CodeInfo['bool']) {
            return ['bool' => false];
        }
        //解析数据
        $wechat_user = $CodeInfo['data'];
        //组装数据
        $params['unionid'] = $wechat_user['unionid'];
        $params['openid'] = $wechat_user['openid'];
        $params['nickname'] = $wechat_user['nickname'];
        $params['avatar'] = $wechat_user['avatar'];
        //获取微信用户信息
        $OpenInfo = $obj->userFromOpenId($wechat_user['openid']);
        $params['subscribe'] = $OpenInfo['bool'] ? intval($OpenInfo['data']['subscribe']) : 0;
        //判断用户是否存在
        $User = User::where(['unionid' => $params['unionid']])->find();
        if (!$User) {
            $User = new User();
            $params['uuid'] = uuid();
            $params['create_time'] = time();
        }
        //写入数据库
        $params['update_time'] = time();
        $User->save($params);
        $user_id = intval($User->id);
        if ($user_id) {
            //返回结果
            return ['bool' => true, 'user_id' => $user_id, 'openid' => $wechat_user['openid'], 'subscribe' => $params['subscribe']];
        }
        return ['bool' => false];
    }

    /**
     * 无塑地球微信H5用户登录
     * @param string $code
     * @return array
     */
    public static function wechatH5Login2(string $code): array
    {
        $obj = new WsOfficialAccount();
        $CodeInfo = $obj->userFromCode($code);
        if (!$CodeInfo['bool']) {
            return ['bool' => false];
        }
        //解析数据
        $wechat_user = $CodeInfo['data'];
        //组装数据
        $params['unionid'] = $wechat_user['unionid'];
        $params['openid'] = $wechat_user['openid'];
        $params['nickname'] = $wechat_user['nickname'];
        $params['avatar'] = $wechat_user['avatar'];
        $params['sex'] = $wechat_user['sex'];
        //判断用户是否存在
        $User = User::where(['unionid' => $params['unionid']])->find();
        if (!$User) {
            $User = new User();
            $params['uuid'] = uuid();
            $params['create_time'] = time();
        }
        //写入数据库
        $params['update_time'] = time();
        $User->save($params);
        $user_id = intval($User->id);
        if ($user_id) {
            //返回结果
            return ['bool' => true, 'user_id' => $user_id, 'openid' => $wechat_user['openid']];
        }
        return ['bool' => false];
    }

    /**
     * 无塑地球微信小程序用户登录
     * @param string $appid
     * @param string $code
     * @return array
     */
    public function miniProgramLogin(string $appid, string $code): array
    {
        //根据appid获取对应小程序参数
        $WayInfo = ScanBagWay::getAppidWay($appid);
        if (!$WayInfo) {
            return ['bool' => false];
        }
        ///实列化小程序组件
        $obj = new ScanBagMiniProgram($WayInfo);
        $CodeInfo = $obj->userFromCode($code);
        if (!isset($CodeInfo['unionid'])) {
            return ['bool' => false, 'info' => $CodeInfo];
        }
        //组装数据
        $params['unionid'] = $CodeInfo['unionid'];
        //判断用户是否存在
        $User = User::where(['unionid' => $params['unionid']])->find();
        if (!$User) {
            $User = new User();
            $params['uuid'] = uuid();
            $params['nickname'] = '环保卫士' . time();
            $params['subscribe'] = 0;
            $params['create_time'] = time();
        }
        //公用参数
        $params['closed'] = 0;
        $params['update_time'] = time();
        //写入数据库
        $User->save($params);
        $user_id = intval($User->id);
        //写入用户微信表
        if ($user_id) {
            //返回结果
            return ['bool' => true, 'user_id' => $user_id];
        }
        return ['bool' => false];
    }

    /**
     * 微信小程序广告ID
     * @param string $appid
     * @return array
     */
    public function miniProgramAdvert(string $appid): array
    {
        //根据appid获取对应小程序参数
        $way = ScanBagWay::getAppidWay($appid);
        if (!$way) {
            return ['bool' => false];
        }
        ///实列化小程序组件
        $wechat = new ScanBagMiniProgram($way);
        return ['bool' => true, 'advert' => $wechat->advert];
    }

    /**
     * 微信验证上传经纬度信息
     * @param string $mac_id
     * @param string $latitude
     * @param string $longitude
     * @return bool
     */
    public static function verifyLatLon(string $mac_id, string $latitude, string $longitude): bool
    {
        //防止为空字符串
        if ($latitude === '' or $longitude === '') {
            return false;
        }
        //获取当前设备的经纬度
        $mac_latLon = Device::findOne(['mac_id' => $mac_id], 'latitude,longitude');
        if (!$mac_latLon) {
            return false;
        }
        //百度坐标转wgs84
        $mac_latLon = LatLon::bd09ToWgs84($mac_latLon['longitude'], $mac_latLon['latitude']);
        //计算两地点之间的距离
        $distance = LatLon::getDistance($longitude, $latitude, $mac_latLon[0], $mac_latLon[1]);
        if ($distance >= 3000) {
            return false;
        }
        return true;
    }

    /**
     * 标记取袋安全码防止用户跳过步骤
     * @param int $user_id
     * @param string $way
     * @param string $mac_id
     * @param int $step
     * @param bool $is_last
     * @return bool
     */
    public static function getBagOutSafeCode(int $user_id, string $way, string $mac_id, int $step, bool $is_last = false): bool
    {
        //获取缓存key
        $key = BaseService::generateBagOutSafeCodeKey($user_id, $way, $mac_id);
        //写入缓存
        if ($step == 1) {
            Cache::set($key, 1, 500);
        } elseif ($step > 1) {
            //判断上一步缓存是否存在
            $value = Cache::get($key);
            if ($value == ($step - 1)) {
                Cache::set($key, $step, 500);
            } else {
                return false;
            }
        }
        //判断当前步骤是否为最后步骤
        if ($is_last) {
            Cache::delete($key);
        }
        return true;
    }

    /**
     * 生成取袋码
     * @param array $userInfo
     * @param string $way
     * @param string $mac_id
     * @param string $ticket
     * @param int $pay_type
     * @return string
     */
    public static function generateBagOutWaycode(array $userInfo, string $way, string $mac_id,string $ticket='',int $pay_type=1): string
    {
        //获取缓存$waycode
        $waycode = BaseService::generateBagOutWaycodeKey($userInfo['id'], $way, $mac_id);
        $data = ['userInfo' => $userInfo, 'way' => $way, 'mac_id' => $mac_id,'pay_type'=>$pay_type];
        if($ticket){
            $data['ticket']=$ticket;
        }
        Cache::set($waycode, $data, 180);
        return $waycode;
    }

    /**
     * 获取取袋码所存的参数
     * @param string $waycode
     * @return mixed
     */
    public static function getBagOutWaycodeParams(string $waycode): mixed
    {
        //获取缓存
        $WaycodeInfo = Cache::get($waycode);
        //删除当前缓存
//        Cache::delete($waycode);
        return $WaycodeInfo;
    }

    /**
     * 免费出袋程序
     * @param string $waycode
     * @param string $way
     * @param int $user_id
     * @param string $mac_id
     * @param int $source  客户端是H5 还是小程序
     * @return array
     */
    public static function bagOutProgram(string $waycode, string $way, int $user_id = 0, string $mac_id = '',int $source=0, int $later = 0): array
    {
        //定义返回值格式
        $return_data = ['bool' => false, 'data' => [], 'msg' => ''];
        //通过waycode获取信息
        $WaycodeInfo = ScanBagV2Service::getBagOutWaycodeParams($waycode);
        //判断路径是否匹配
        if (!$WaycodeInfo or $way != $WaycodeInfo['way']) {
//            return array_merge($return_data, ['msg' => '取袋路径错误,请重新扫码二维码!']);
        }
        //判断缓存是否有策略
        $location_limit = 0;
        $tKey = BaseService::generateScanBagStrategyKey($user_id);
        $WayInfo = Cache::get($tKey);
        if($WayInfo){
            $location_limit = json_decode($WayInfo,true)['location_limit'];
        }
        if($location_limit){
            //获取缓存当前用户经纬度
            $LatLon = Cache::get(BaseService::generateLocationKey($user_id));
            if (!$LatLon or !isset($LatLon['latitude']) or !isset($LatLon['longitude'])) {
                return jsonReturn(0, [], '您的位置授权失败,请重新扫描设备二维码!');
            }
            //验证地理位置
            $bool = ScanBagV2Service::verifyLatLon($mac_id, $LatLon['latitude'], $LatLon['longitude']);
            if (!$bool) {
                return jsonReturn(0, [], '您的位置距离设备太远,请移步设备前扫码免费领取!');
            }
        }
        if ($user_id and $mac_id and ($user_id != $WaycodeInfo['userInfo']['id'] or $mac_id != $WaycodeInfo['mac_id'])) {
            return array_merge($return_data, ['msg' => '取袋路径错误,请重新扫码二维码!']);
        } else {
            $user_id = $WaycodeInfo['userInfo']['id'];
            $mac_id = $WaycodeInfo['mac_id'];
        }
        //验证当前用户当天是否出了3次袋子
        $day = ScanBagDay::getUserIdDayNum($user_id);
        if ($day['day_surplus'] <= 0) {
            return array_merge($return_data, ['msg' => '您当天免费的取袋次数已用完!']);
        }
        //获取当前设备imei
        $where = ['mac_id' => $mac_id, 'online' => 1, 'closed' => 0];
        $imei = Device::where($where)->value('imei');
        if (!$imei) {
            return array_merge($return_data, ['msg' => '设备状态异常!']);
        }
        //生成订单队列
        return self::generateOrderQueue($user_id, $mac_id, $imei,$source,$later);
    }

    /**
     * 生成订单队列
     * @param int $user_id
     * @param string $mac_id
     * @param string $imei
     * @param int $source  客户端是H5 还是小程序
     * @param int $later  延迟执行时间
     * @return array
     */
    private static function generateOrderQueue(int $user_id, string $mac_id, string $imei,int $source=0,$later = 0): array
    {
        //生成订单号
        $order_no = generateOrderNo();
        //写入数据库出袋日志
        $order_data = [
            'user_id' => $user_id,
            'mac_id' => $mac_id,
            'imei' => $imei,
            'order_no' => $order_no,
            'pay_pattern' => '免费',
            'pay_type' => '免费',
            'pay_state' => '免费',
            'money' => floatval(0),
            'integral' => 0,
        ];
        $order_bool = ScanBagOrder::writeOutBagOrder($order_data);
        if (!$order_bool) {
            return ['bool' => false, 'data' => [], 'msg' => '出袋失败,设备网络信号不稳定!'];
        }
        //生成key写入缓存
        $tKey = BaseService::generateScanBagQueueKey($mac_id);
        $redis = RedisTools2::getInstance(config('cache.stores.redis'));
        $wait_num = $redis->rPush($tKey, json_encode($order_data));
        $redis->expire($tKey, 86400);
        //写入出袋队列
        ThinkQueueJob::sendMsg('ProcessOrderQueue', '', $order_data, $later);
        //返回值
        $wait_status = $wait_num > 1 ? 1 : 2;
        $wait_msg = self::$EnumQueueStatus[$wait_status];
        $emqx = new Emqx();
        $data = [
            'order_no' => $order_no,
            'wait_status' => $wait_status,
            'wait_msg' => $wait_msg,
            'wait_num' => $wait_num,
            'wait_total' => $wait_num,
            'host' => $source>0?$emqx->emqx_wss_websocket_host:$emqx->emqx_scan_bag_host,
            'topic' => $emqx->emqx_scan_bag_topic_js . $order_no,
            'username' => $emqx->emqx_scan_bag_username,
            'password' => $emqx->emqx_scan_bag_password,
        ];
        return ['bool' => true, 'data' => $data, 'msg' => $wait_msg];
    }

    /**
     * 微信取袋发起预支付
     * @param int $user_id
     * @param string $mac_id
     * @return array
     */
    public static function wechatPayOrder(int $user_id, string $mac_id): array
    {
        //获取用户信息
        $user_info = User::getIdUserInfo($user_id);
        if (!$user_info) {
            return ['bool' => false, 'data' => [], 'msg' => '用户信息错误,请重新登录!'];
        }
        //获取设备信息
        $device = Device::findOne(['mac_id' => $mac_id]);
        if (!$device) {
            return ['bool' => false, 'data' => [], 'msg' => '机器信息不存在,请联系管理员!'];
        }
        //判断当前设备是否在线,如离线不让下单
        if ($device['install'] != 1 or $device['online'] != 1) {
            return ['bool' => false, 'data' => [], 'msg' => '机器网络不稳定,请稍后重试!'];
        }
        //判断当前设备袋子余量
        if ($device['allowance'] <= 1) {
            return ['bool' => false, 'data' => [], 'msg' => '机器袋子余量不足,请联系管理员!'];
        }
        //预支付创建订单
        $order_no = generateOrderNo();
        $money = env('ENVIRONMENT') == 'dev' ? 0.01 : 1;
        //写入数据库出袋日志
        $order_data = [
            'user_id' => $user_id,
            'mac_id' => $mac_id,
            'order_no' => $order_no,
            'pay_pattern' => '在线支付',
            'pay_type' => '微信支付',
            'pay_state' => '支付失败',
            'money' => floatval($money),
            'integral' => 0,
        ];
        $order_bool = ScanBagOrder::writeOutBagOrder($order_data);
        if (!$order_bool) {
            return ['bool' => false, 'data' => [], 'msg' => '发起支付失败,请重新操作!'];
        }
        //发起预支付
        $OfficialAccount = new WsOfficialAccount();
        $appid = $OfficialAccount->appid;
        $openid = $user_info['openid'];
        $description = '购买环保袋';
        //支付通知回调
        $notify_url = env('ApiDomainV2') . '/callback/wechatPayNoticeScanBag';
        $WechatPay = new WechatPay();
        $pay = $WechatPay->payTransactionsJsapi($openid, $order_no, $order_data['money'], $appid, $description, $notify_url);
        if (!$pay['bool']) {
            return ['bool' => false, 'data' => [], 'msg' => $pay['msg']];
        }
        //生成前端调起支付需要的参数
        $data = $WechatPay->jsPaySignMd5($appid, $pay['msg']['prepay_id']);
        return ['bool' => true, 'data' => $data, 'msg' => '发起成功'];
    }

    /**
     * 微信支付出袋回调
     * @param array $inBodyResourceArray
     * @return array
     */
    public function scanBagOrder(array $inBodyResourceArray): array
    {
        $out_trade_no = $inBodyResourceArray['out_trade_no'];
        $trade_state = $inBodyResourceArray['trade_state'];
        if ($trade_state != 'SUCCESS') {
            return ['bool' => false, 'msg' => '支付失败!'];
        }
        //获取当前订单状态
        $where = ['order_no' => $out_trade_no];
        $Order = ScanBagOrder::findOne($where);
        if (!$Order or $Order['pay_state'] == '支付成功') {
            return ['bool' => false, 'msg' => '订单状态异常或已处理!'];
        }
        //支付成功,更新订单状态
        ScanBagOrder::update(['pay_state' => '支付成功'], $where);
        //获取当前设备imei
        $where = ['mac_id' => $Order['mac_id'], 'online' => 1, 'closed' => 0];
        $imei = Device::where($where)->value('imei');
        if (!$imei) {
            return ['bool' => false, 'msg' => '设备状态异常!'];
        }
        //下发指令出袋
        $Client = DeviceClient::getInstance();
        $res = $Client->shippingOrder($imei, $out_trade_no);
        if (!$res) {
            return ['bool' => false, 'msg' => '出袋失败,设备网络信号不稳定!'];
        }
        return ['bool' => true, 'msg' => '出袋成功!'];
    }

}
